<?php
/**
 * Created by PhpStorm.
 * User: lhl
 * Date: 2019/6/5
 * Time: 10:45
 * Features: 单例模式redis封装
 */

namespace app\common\service;


use think\Exception;
use think\facade\Config;

class RedisClient
{
    //客户端对象
    private static $_client;
    //redis对象
    private $_redis;
    //配置信息
    protected $_config;

    private function __construct(){
        //读取配置
        $this->_config = Config::get('redis.',[
            'host' => '127.0.0.1',
            'port' => 6379,
            'password' => '',
            'cache_db'   => 0
        ]);

        try{
            $this->_redis = new \Redis();
            $this->_redis->connect($this->_config['host'],$this->_config['port']);
            $this->_redis->auth($this->_config['password']);
        }catch (\RedisException $e){
            throw new Exception('Redis连接失败');
        }
    }

    private function __clone(){}

    /**
     * 获取Client对象
     * 用于调用本类的方法
    */
    public static function getInstance(){
        if(!self::$_client instanceof self){
            self::$_client = new self();
        }
        return self::$_client;
    }

    /**
     * 获取redis对象
     * 可用于调用原生\Redis类的方法
    */
    public function getRedis(){
        return $this->_redis;
    }

    /**
     * 选择存储的库
     * @param int $db 库编号
     */
    public function select($db = 0){
        return $this->_redis->select($db);
    }

    /**
     * string类型 - 设置值
     * @param string       $key     key名称
     * @param string|array $value   值
     * @param int          $timeOut 有效时间
     * @param int          $db      库编号,null表示使用配置的默认缓存库
     * @param bool         $json    是否json存储
     */
    public function set($key, $value, $timeOut = 0, $db = null,$json = true)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value, true);
        }
        if($timeOut){
            return $this->_redis->set($key, $value,$timeOut);
        }else{
            return $this->_redis->set($key, $value);
        }
    }

    /**
     * string类型 - 根据key获取值
     * @param string $key  key名称
     * @param int    $db   库编号,null表示使用配置的默认缓存库
     * @param bool   $json 是否json存储
     */
    public function get($key, $db = null,$json = true)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $value = $this->_redis->get($key);
        if($json && $value){
            $value = json_decode($value,true);
        }
        return $value;
    }

    /**
     * sting类型 - 当key不存在时设置值
     * @param  string        $key    key名称
     * @param  string|array  $value  值
     * @param  int           $db     库编号,null表示使用配置的默认缓存库
     * @param  bool          $json   是否json存储
     */
    public function setnx($key, $value, $db = null,$json = true)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value, true);
        }
        return $this->_redis->setnx($key, $value);
    }

    /**
     * 获取锁  - 实现锁机制
     * @param  string  $key    key名称
     * @param  int     $expire 锁过期时间
     * @param  int     $db     库编号,null表示使用配置的默认缓存库
     * @return bool    true:   未在锁定状态  false: 正在锁定状态
     */
    public function lock($key,$expire = 2,$db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        //加锁(从Redis 2.6.12开始，通过set指令可选参数也可以实现setnx，同时可原子化地设置超时时间)
        $isLock = $this->_redis->set($key, 1, ['nx', 'ex' => $expire]);
        return $isLock ? true : false;
    }

    /**
     * 查看key有效时间
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function ttl($key,$db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->ttl($key);
    }

    /**
     * 设置key的过期时间
     * @param string $key     key名称
     * @param int    $timeOut 有效时间
     * @param int    $db      库编号,null表示使用配置的默认缓存库
    */
    public function expire($key,$timeOut,$db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->expire($key,$timeOut);
    }

    /**
     * 根据key删除一条数据
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function delete($key, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        //redis 5版本以上，不再支持delete方法
        return $this->_redis->del($key);
    }

    /**
     * 清空所有库的所有数据
     */
    public function flushAll()
    {
        return $this->_redis->flushAll();
    }

    /**
     * 数据自增+1
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function incr($key,$db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->incr($key);
    }

    /**
     * 数据自增指定数
     * @param string $key  key名称
     * @param int    $num  递增量
     * @param int    $db   库编号,null表示使用配置的默认缓存库
     */
    public function incrBy($key,$num = 1,$db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->incrBy($key,$num);
    }

    /**
     * 数据自减-1
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function decr($key,$db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->decr($key);
    }

    /**
     * 数据自减指定数
     * @param string $key  key名称
     * @param int    $num  递减量
     * @param int    $db   库编号,null表示使用配置的默认缓存库
     */
    public function decrBy($key,$num = 1,$db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->decrBy($key,$num);
    }

    /**
     * 判断key是否存在
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function exists($key, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->exists($key);
    }

    /**
     * 查询该数据库中所有符合规则的key名
     * @param string $key key名称规则,*为通配符
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function keys($key = '*', $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->keys($key);
    }

    /**
     * list类型 - 数据入队列
     * @param string       $key    key名称
     * @param string|array $value  获取得到的数据
     * @param int          $db     库编号,null表示使用配置的默认缓存库
     * @param bool         $json   是否json存储
     * @param bool         $right  是否从右边开始入
     */
    public function push($key, $value, $db = null,$json = false,$right = true)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value);
        }
        return $right ? $this->_redis->rPush($key, $value) : $this->_redis->lPush($key, $value);
    }

    /**
     * list类型 - 数据出队列
     * @param string $key   key名称
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     * @param bool   $json  是否json存储
     * @param bool   $left  是否从左边开始出
     */
    public function pop($key, $db = null,$json = false,$left = true)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $val = $left ? $this->_redis->lPop($key) : $this->_redis->rPop($key);
        if($json && $val){
            $val = json_decode($val,true);
        }
        return $val;
    }

    /**
     * list类型 - 查询队列长度
     * @param string $key key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function lLen($key, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->lLen($key);
    }

    /**
     * list类型 - 查询队列中指定区间内的元素
     * @param string $key   key名称
     * @param int    $start 起始偏移量 0代表第一个元素 -1代表最后1个元素 以此类推
     * @param int    $end   终点偏移量
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     * @param bool   $json  是否json存储
     */
    public function lRange($key, $start = 0, $end = -1, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $list =  $this->_redis->lRange($key, $start, $end);
        if($json && $list){
           $list = array_map('json_decode',$list);
        }
        return $list;
    }

    /**
     * list类型 - 根据参数 $count 的值，移除列表中与参数 $value 相等的元素。
     * @param string $key    key名称
     * @param string $value  要移除的元素的值
     * @param int    $count  要移除的数量
     *        $count > 0 :   从头部开始向尾部搜索，移除与 $value 相等的元素，数量为 $count 。
     *        $count < 0 :   从尾部开始向头部搜索，移除与 $value 相等的元素，数量为 $count 的绝对值。
     *        $count = 0 :   移除队列中所有与 $value 相等的值。
     * @param int    $db     库编号,null表示使用配置的默认缓存库
    */
    public function lRem($key, $value, $count = 0, $db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->lRem($key, $value, $count);
    }

    /**
     * list类型 - 通过索引来设置元素的值
     * @param string $key   key名称
     * @param string $index 该元素对应索引下标
     * @param string $value 值
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     * @param bool   $json  是否json存储
     */
    public function lSet($key,$index,$value,$db = null,$json = false){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value,true);
        }
        return $this->_redis->lSet($key, $index, $value);
    }

    /**
     * list类型 - 通过索引获取列表中的元素
     * @param string $key    key名称
     * @param int    $index  该元素对应索引下标
     * @param int    $db     库编号,null表示使用配置的默认缓存库
     * @param bool   $json   是否json存储
     */
    public function lIndex($key,$index,$db = null,$json = false){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $val = $this->_redis->lIndex($key,$index);
        if($json && $val){
            $val = json_decode($val,true);
        }
        return $val;
    }

    /**
     * list类型 - 对一个列表进行修剪,只保留指定区间内的元素
     * @param string $key    key名称
     * @param int    $start  开始位置索引下标
     * @param int    $stop   结束位置索引效标
     * @param int    $db     库编号,null表示使用配置的默认缓存库
     */
    public function lTrim($key,$start,$stop,$db = null){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->lTrim($key, $start, $stop);
    }

    /**
     * list类型 - 在列表的元素前或者后插入元素
     * @param  string $key        key名称
     * @param  string $existValue 队列中已存在的值
     * @param  string $value      要插入的值
     * @param  string $position   before | after
     * @param  int    $db         库编号,null表示使用配置的默认缓存库
     * @param  bool   $json       是否json存储
     * @return int    如果命令执行成功，返回列表的长度。如果没有找到指定元素,返回 -1.如果 key不存在或为空列表,返回0
     */
    public function lInsert($key,$existValue,$value,$position = 'after',$db = null,$json = false){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value);
        }
        return $this->_redis->lInsert($key,$position,$existValue,$value);
    }

    /**
     * list类型 - 移除列表的最后一个元素，并将该元素添加到另一个列表的头部
     * @param  string $srcKey  源队列key名称
     * @param  string $dstKey  目标队列key名称
     * @param  int    $db      库编号,null表示使用配置的默认缓存库
     * @param  bool   $block   是否阻塞等待
     * @param  int    $timeOut 阻塞等待时间
     */
    public function rpoplpush($srcKey,$dstKey,$db = null,$block = false,$timeOut = 0){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if(!$block){
            $res = $this->_redis->rpoplpush($srcKey,$dstKey);
        }else{
            $res = $this->_redis->brpoplpush($srcKey,$dstKey,$timeOut);
        }
        return $res;
    }

    /**
     * hash类型 - 向hash添加单个元素
     * @param string $key     整个hash的key名称
     * @param string $hashKey 该元素的key名称
     * @param string $value   值
     * @param int    $db      库编号,null表示使用配置的默认缓存库
     * @param bool   $json    是否json存储
     */
    public function hSet($key, $hashKey, $value, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value,true);
        }
        return $this->_redis->hSet($key,$hashKey,$value);
    }

    /**
     * hash类型 - 向hash批量添加元素
     * @param string $key       整个hash的key名称
     * @param array  $hashKeys  hash键值对数组
     * @param int    $db        库编号,null表示使用配置的默认缓存库
     * @param bool   $json      是否json存储
     */
    public function hMset($key, $hashKeys, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $hashKeys = array_map('json_encode',$hashKeys);
        }
        return $this->_redis->hMset($key, $hashKeys);
    }

    /**
     * hash类型 - 获取hash中单个元素
     * @param string $key     整个hash的key名称
     * @param string $hashKey 该元素的key名称
     * @param int    $db      库编号,null表示使用配置的默认缓存库
     * @param bool   $json    是否json存储
     */
    public function hGet($key,$hashKey,$db = null,$json = false){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $value = $this->_redis->hGet($key,$hashKey);
        if($json && $value){
            $value = json_decode($value,true);
        }
        return $value;
    }

    /**
     * hash类型 - 获取hash中多个元素
     * @param string $key      整个hash的key名称
     * @param array  $hashKeys 该元素的key名称数组
     * @param int    $db       库编号,null表示使用配置的默认缓存库
     * @param bool   $json     是否json存储
     */
    public function hMget($key,$hashKeys,$db = null,$json = false){
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $value = $this->_redis->hMGet($key,$hashKeys);
        if($json && $value){
            $value = array_map('json_decode',$value);
        }
        return $value;
    }

    /**
     * hash类型 - 获取hash中所有元素
     * @param string $key   整个hash的key名称
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     * @param bool   $json  是否json存储
     */
    public function hGetAll($key, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $value = $this->_redis->hGetAll($key);
        if($json && $value){
            $value = array_map('json_decode',$value);
        }
        return $value;
    }

    /**
     * hash类型 - 获取hash中元素数
     * @param string $key  整个hash的key名称
     * @param int    $db  库编号,null表示使用配置的默认缓存库
     */
    public function hLen($key, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->hLen($key);
    }

    /**
     * hash类型 - 删除hash中的某个元素
     * @param string $key     整个hash的key名称
     * @param string $hashKey 该元素的key名称
     * @param int    $db      库编号,null表示使用配置的默认缓存库
     */
    public function hDel($key, $hashKey, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->hDel($key, $hashKey);
    }

    /**
     * hash类型 - 查询hash中所有元素的hashKey
     * @param string $key  整个hash的key名称
     * @param int    $db   库编号,null表示使用配置的默认缓存库
     */
    public function hKeys($key, $db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->hKeys($key);
    }

    /**
     * hash类型 - hash中某元素自增指定整数
     * @param string $key     整个hash的key名称
     * @param string $hashKey 该元素的key名称
     * @param int    $num     增加的整数
     * @param int    $db      库编号,null表示使用配置的默认缓存库
     */
    public function hIncrBy($key, $hashKey, $num = 1,$db = null)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        return $this->_redis->hIncrBy($key, $hashKey, $num);
    }

    /**
     * set类型 - 向集合中添加元素
     * @param string $key   整个集合的key名称
     * @param string $value 值
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     */
    public function sAdd($key, $value, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        if($json){
            $value = json_encode($value,true);
        }
        return $this->_redis->sAdd($key, $value);
    }

    /**
     * 移除并返回集合中的指定key的随机元素
     * @param string $key   整个集合的key名称
     * @param int    $db    库编号,null表示使用配置的默认缓存库
     * @param bool   $json  是否json存储
     */
    public function sPop($key, $db = null,$json = false)
    {
        $db = is_null($db) ? $this->_config['cache_db'] : $db;
        $this->_redis->select($db);
        $value = $this->_redis->sPop($key);
        if($json && $value){
            $value = json_decode($value,true);
        }
        return $value;
    }

}